import { fork } from 'child_process';
import { ServerSide } from '../ipc.js';
import { EventEmitter } from 'events';
import terminate from 'terminate/promise.js';

class Scripter extends EventEmitter {
    constructor({ path, env }) {
        super();
        this._path = path;
        this._env = env;
        this._process = null;
        this._running = false;
        this._stopped = false;
    }

    get path() {
        return this._path;
    }

    get env() {
        return this._env;
    }

    get pid() {
        if (this._process) {
            return this._process.pid;
        }
        return null;
    }

    async start() {
        if (this._stopped) {
            return;
        }
        return new Promise((resolve, reject) => {
            let child = fork(path, { env });
            child.on('close', () => {
                this._running = false;
                this._process = null;
                if (this._stopped) {
                    this.emit('close', this);
                } else {
                    setTimeout(() => this.start(), 1000);
                }
            });
            child.once('error', e => reject(e));
            child.once('spawn', () => {
                this._process = child;
                this._running = true;
                resolve();
            });
        });
    }

    async stop() {
        this._stopped = true;
        if (this._process) {
            return terminate(this._process.pid);
        }
    }

    async restart() {
        await this.stop();
        this._stopped = false;
        return this.start();
    }

    getStatus() {
        return {
            pid: this._process ? this._process.pid : null,
            mpid: process.pid
        };
    }
}

export class ProcessManager {
    constructor() {
        this._children = [];
    }

    startup() {
        this._server = new ServerSide();
        return this._server.startup().then(() => {
            this._server.observe('start', ({ path, env }) => {
                let child = this._children.push(new Scripter({ path, env }));
                child.on('close', () => this._children.splice(this._children.indexOf(child), 1));
                child.startup().then(() => {
                    return { data: { code: 0, data: child.getStatus() } };
                }).catch(e => ({ data: { code: 1, error: e.message } }));
            });
            this._server.observe('stop', ({ path, pid }) => {
                let r = { success: [], error: [] };
                return Promise.all(this.getChildren({ path, pid }).map(child => {
                    return child.stop().then(() => {
                        r.success.push(child.pid);
                    }).catch(e => {
                        r.error.push({ error: e.message, pid: child.pid });
                    });
                })).then(() => {
                    return { data: { code: 0, data: r } };
                });
            });
            this._server.observe('restart', ({ path, pid }) => {
                let r = { success: [], error: [] };
                return Promise.all(this.getChildren({ path, pid }).map(child => {
                    return child.restart().then(() => {
                        r.success.push(child.pid);
                    }).catch(e => {
                        r.error.push({ error: e.message, pid: child.pid });
                    });
                })).then(() => {
                    return { data: { code: 0, data: r } };
                });
            });
            this._server.observe('status', ({ path, pid }) => {
                let list = this.getChildren({ path, pid }).map(a => {
                    return a.getStatus();
                });
                return { data: { code: 0, data: list } };
            });
            process.send({ type: 'initialize', code: 0 });
        }).catch(e => {
            process.send({ type: 'initialize', code: 1, error: e.message });
        });
    }

    getChildren({ path, pid }) {
        return this._children.filter(a => {
            if (a.path === path) {
                if (pid) {
                    return a.pid === pid;
                }
            }
            return true;
        });
    }
}

(new ProcessManager()).startup();